/* Needed to get IUnknown_ interface working correctly! */
#define COBJMACROS

#include <windows.h>
#include <ddraw.h>
#include <unknwn.h>
#include <stdio.h>
#include "wine/debug.h"

#define _32XRGB(r,g,b) ((b) + ((g) << 8) + ((r) << 16))
#define TEST_TIMEOUT 3000.0

WINE_DEFAULT_DEBUG_CHANNEL(dxdiag);

/* Global handlers */
HWND gddw_hWnd = NULL;
LPDIRECTDRAW gddw_lpdd = NULL;
LPDIRECTDRAW7 gddw_lpdd7 = NULL;

/* Resolution parameters */
int gddw_width = 800;
int gddw_height = 600;
int gddw_bpp = 32;

/* Time variables */
double gddw_t_cur;
double gddw_t_last;
double gddw_t_elapsed;

/* Description of surface */
DDSURFACEDESC2 gddw_ddsd;

/* Primary and back buffer surface */
LPDIRECTDRAWSURFACE7 gddw_lpPrimary = NULL;
LPDIRECTDRAWSURFACE7 gddw_lpBack = NULL;

/* Is a test instance currently running? */
volatile int gddw_running = FALSE;

/* Other globals */
int gddw_lpitch = 0;
DWORD *gddw_lpscreen = NULL;
RECT gddw_winRect;

void DDrawTest_initDDraw(void);
void DDrawTest_render(void);
void DDrawTest_shutdown(void);
void DDrawTest_eraseDesc(void);
void DDrawTest_LockSurface(LPDIRECTDRAWSURFACE7 surface);
void DDrawTest_UnlockSurface(LPDIRECTDRAWSURFACE7 surface);

LRESULT CALLBACK DDrawTest_DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

void DDrawTest_testDDraw()
{
    int retval;
    WNDCLASSEX wc = {0};
    MSG uMsg;

    /* TODO: Need atomic locking */
    if (gddw_running)
        return;
    gddw_running = TRUE;

    retval = MessageBox(NULL, "This will test Direct Draw 7 on this device. Okay to proceed?","Direct Draw Test",MB_YESNO);

    if (retval != IDYES)
    {
        gddw_running = FALSE;
        return;
    }

    memset(&uMsg,0,sizeof(uMsg));
    wc.lpszClassName = "dxdiag";
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = DDrawTest_DlgProc;

    RegisterClassEx(&wc);

    gddw_hWnd = CreateWindowEx(0, "dxdiag", "Direct Draw Test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, gddw_width, gddw_height, 0, 0, 0, 0);

    ShowWindow(gddw_hWnd, SW_SHOWNORMAL);
    UpdateWindow(gddw_hWnd);

    DDrawTest_initDDraw();

    gddw_t_elapsed = 0;
    gddw_t_last = timeGetTime();

    DDrawTest_render();

    while (uMsg.message != WM_QUIT)
    {
        if (gddw_t_elapsed >= TEST_TIMEOUT)
        {
            PostQuitMessage(0);
            DestroyWindow(gddw_hWnd);
        }
        
        if (PeekMessage(&uMsg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&uMsg);
            DispatchMessage(&uMsg);
        }
        gddw_t_cur = timeGetTime();
        gddw_t_elapsed += gddw_t_cur - gddw_t_last;
        gddw_t_last = gddw_t_cur;
    }

    DDrawTest_shutdown();
    MessageBox(NULL,"Did you see the cocentric rectangles on the screen?","Direct Draw Test",MB_YESNO);
    UnregisterClass("dxdiag", wc.hInstance);
    gddw_running = FALSE;
}

void DDrawTest_initDDraw()
{
    if (FAILED(DirectDrawCreate(NULL, &gddw_lpdd, NULL)))
    {
        WINE_ERR( "DirectDrawCreate() - Failed\n");
        abort();
    }

    if (FAILED(IUnknown_QueryInterface(gddw_lpdd, &IID_IDirectDraw7, (LPVOID)&gddw_lpdd7)))
    {
        WINE_ERR( "Failed to query interface for DirectDraw7\n");
        abort();
    }

    if (gddw_lpdd)
    {
        IDirectDraw_Release(gddw_lpdd);
        gddw_lpdd = NULL;
    }

    /* Set the cooperative level of DirectDraw7 to windowed mode */
    if (FAILED(IDirectDraw7_SetCooperativeLevel(gddw_lpdd7, gddw_hWnd,DDSCL_NORMAL)))
    {
        WINE_ERR( "Failed to set DirectDraw cooperative level\n");
        abort();
    }

    /* Don't need to change the display mode in a windowed application
     * if (FAILED(IDirectDraw7_SetDisplayMode(gddw_lpdd7,gddw_width,gddw_height,gddw_bpp,0,0))) {
     *    WINE_ERR("Failed to set display mode\n");
     *   abort();
     *   }
     */

    /* Create a primary surface with no back buffer */
    DDrawTest_eraseDesc();
    gddw_ddsd.dwFlags = DDSD_CAPS;
    gddw_ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    if (FAILED(IDirectDraw7_CreateSurface(gddw_lpdd7, &gddw_ddsd, &gddw_lpPrimary, NULL)))
    {
        WINE_ERR( "Failed to create the primary surface\n");
        abort();
    }

    /* DOUBLE BUFFERING IN DIRECTDRAW IS NOT SUPPORTED! */
    /* Create a primary surface with 1 back buffer */
    DDrawTest_eraseDesc();
    gddw_ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    gddw_ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_VIDEOMEMORY;
    gddw_ddsd.dwBackBufferCount = 1;

    if (FAILED(IDirectDraw7_CreateSurface(gddw_lpdd7, &gddw_ddsd, &gddw_lpPrimary, NULL)))
    {
        WINE_ERR( "Failed to create the primary surface\n");
        abort();
    }

    /* Get the pointer to the back buffer */
    gddw_ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    if(FAILED(IDirectDrawSurface7_GetAttachedSurface(gddw_lpPrimary, &gddw_ddsd.ddsCaps, &gddw_lpBack)))
    {
        WINE_ERR( "Failed to get back buffer pointer\n");
        abort();
    }

    /* Initialize window coordinates */
    GetWindowRect(gddw_hWnd, &gddw_winRect);
    gddw_width = gddw_winRect.right - gddw_winRect.left;
    gddw_height = gddw_winRect.bottom - gddw_winRect.top;
}

void DDrawTest_eraseDesc()
{
    memset(&gddw_ddsd, 0, sizeof(DDSURFACEDESC2));
    gddw_ddsd.dwSize = sizeof(DDSURFACEDESC2);
}

/* DirectDraw rendering */
void DDrawTest_render()
{
    UINT i;
    DDBLTFX fx;
    RECT rect;
    ZeroMemory(&fx, sizeof(fx));
    fx.dwSize = sizeof(fx);
    fx.dwFillColor = 0;

    if (FAILED(IDirectDrawSurface7_Blt(gddw_lpPrimary, &gddw_winRect, NULL, NULL, DDBLT_ASYNC | DDBLT_COLORFILL, &fx)))
        IDirectDrawSurface7_Blt(gddw_lpPrimary, &gddw_winRect, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);

    for (i = 0; i < 25; i++)
    {
        rect.left = gddw_winRect.left + (i*gddw_width)/50;
        rect.right = gddw_winRect.right - (i*gddw_width)/50;
        rect.top = gddw_winRect.top + (i*gddw_height)/50;
        rect.bottom = gddw_winRect.bottom - (i*gddw_height)/50;
        if (i % 2)
            fx.dwFillColor = _32XRGB(255,255,255);
        else
            fx.dwFillColor = 0;

        /* Attempt to blit asynchronously; if that fails, blit synchronously */
        if (FAILED(IDirectDrawSurface7_Blt(gddw_lpPrimary, &rect, NULL, NULL, DDBLT_ASYNC | DDBLT_COLORFILL, &fx)))
            IDirectDrawSurface7_Blt(gddw_lpPrimary, &rect, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
    }

    /* Method to gain direct access to video memory */
    /* Lock surface before drawing */
    DDrawTest_LockSurface(gddw_lpPrimary);

    /* DRAW SCENE HERE */

    /* Unlock surface after drawing
    DDrawTest_UnlockSurface(gddw_lpPrimary);
    */
}

/* Draws pixel in screen coordinates */
void DDrawTest_drawPixel(int x, int y, int r, int g, int b)
{
    /* Check bounds */
    if (x >= gddw_ddsd.dwWidth || x < 0)
        return;
    else if (y >= gddw_ddsd.dwHeight || y < 0)
        return;
    /* If everything's okay, go ahead and set the color */
    gddw_lpscreen[x + y * (gddw_lpitch >> 2)] = _32XRGB(r,g,b);
}

void DDrawTest_shutdown()
{
    if (gddw_lpdd7) IDirectDraw7_Release(gddw_lpdd7);
    if (gddw_lpdd) IDirectDraw_Release(gddw_lpdd);
    if (gddw_lpPrimary) IDirectDrawSurface7_Release(gddw_lpPrimary);
}

/* TODO: Return values! */
void DDrawTest_LockSurface(LPDIRECTDRAWSURFACE7 surface)
{
    DDrawTest_eraseDesc();

    if (FAILED(IDirectDrawSurface7_Lock(surface, NULL, &gddw_ddsd, DDLOCK_WAIT, NULL)))
        WINE_ERR("IDirectDrawSurface7 - Failed to lock the surface\n");

    gddw_lpitch = (int)gddw_ddsd.lPitch;
    gddw_lpscreen = (DWORD*)gddw_ddsd.lpSurface;
}

void DDrawTest_UnlockSurface(LPDIRECTDRAWSURFACE7 surface)
{
    IDirectDrawSurface7_Unlock(surface, NULL);
    gddw_lpitch = 0;
    gddw_lpscreen = NULL;
}

LRESULT CALLBACK DDrawTest_DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_KEYDOWN:
            switch (wParam)
            {
                case VK_ESCAPE:
                    PostQuitMessage(0);
                    DestroyWindow(hWnd);
                    break;
            }
        case WM_CLOSE:
        case WM_DESTROY:
            PostQuitMessage(0);
            DestroyWindow(hWnd);
        break;
        case WM_SIZE:
            GetWindowRect(gddw_hWnd, &gddw_winRect);
            gddw_width = gddw_winRect.right - gddw_winRect.left;
            gddw_height = gddw_winRect.bottom - gddw_winRect.top;
            break;
        case WM_MOVE:
            GetWindowRect(gddw_hWnd, &gddw_winRect);
            break;

        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
            break;
    }
    return 0;
}
